﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using iNet.Collections;

namespace iNet.Media.Imaging.Exif
{
	/// <summary>
	/// IFD 寫入元件。
	/// </summary>
	public class IfdWriter : IExifTagWriter
	{
		#region TagInfo
		class TagInfo
		{
			public long? DataOffset;
			public readonly ExifTag Tag;
			public TagInfo(ExifTag tag, long? dataOffset)
			{
				this.Tag = tag;
				this.DataOffset = dataOffset;
			}
		}
        #endregion
		#region 欄位
		readonly BitConverter _BitConverter;
		readonly MemoryStream _IfdDataStream = new MemoryStream();
		readonly long _ReferenceStreamPosition;
		readonly Stream _Stream;
		readonly HashSet<ushort> _TagIdTable = new HashSet<ushort>();
		readonly List<TagInfo> _Tags = new List<TagInfo>();
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 IfdWriter 執行個體。
		/// </summary>
		/// <param name="stream">提供 IFD 寫入的資料流。</param>
		/// <param name="refStreamPosition">資料流起始參考位置，資料位移的計算將參考此值。</param>
		/// <param name="bitConverter">提供位元組轉換的 BitConverter。</param>
		/// <exception cref="ArgumentNullException">stream 或 bitConverter 為 Null (Visual Basic 為 Nothing)。</exception>
		/// <exception cref="ArgumentOutOfRangeException">refStreamPosition 為負整數。</exception>
		public IfdWriter(Stream stream, long refStreamPosition, BitConverter bitConverter)
		{
			//確認參數
			if (stream == null)
				throw new ArgumentNullException("stream");
			if (bitConverter == null)
				throw new ArgumentNullException("bitConverter");
			if (refStreamPosition < 0)
				throw new ArgumentOutOfRangeException();

			//初始化
			_Stream = stream;
			_ReferenceStreamPosition = refStreamPosition;
			_BitConverter = bitConverter;
		}
		#endregion
		#region BitConverter
		/// <summary>
		/// 取得提供位元組轉換的 BitConverter。
		/// </summary>
		public BitConverter BitConverter
		{
			get
			{
				return _BitConverter;
			}
		}
		#endregion
		#region Commit
		/// <summary>
		/// 將 EXIF Tag 寫入資料流。
		/// </summary>
		/// <exception cref="IOException">資料流存取錯誤。</exception>
		public void Commit()
		{
			IDictionary<ExifTagKey, long> tagPositions;
			this.Commit(out tagPositions);
		}
		/// <summary>
		/// 將 EXIF Tag 寫入資料流。
		/// </summary>
		/// <param name="tagPositions">EXIF Tag 在資料流中的位置。</param>
		/// <exception cref="IOException">資料流存取錯誤。</exception>
		public void Commit(out IDictionary<ExifTagKey, long> tagPositions)
		{
			long nextIfdFieldPosition;
			this.Commit(out tagPositions, out nextIfdFieldPosition);
		}
		/// <summary>
		/// 將 EXIF Tag 寫入資料流。
		/// </summary>
		/// <param name="tagPositions">EXIF Tag 在資料流中的位置。</param>
		/// <param name="nextIfdFieldPosition">指向下一個 IFD 位置的欄位。</param>
		/// <exception cref="IOException">資料流存取錯誤。</exception>
		public void Commit(out IDictionary<ExifTagKey, long> tagPositions, out long nextIfdFieldPosition)
		{
			//初始化參數
			tagPositions = new Dictionary<ExifTagKey, long>();
			nextIfdFieldPosition = 0;

			//確認狀態
			if (_Tags.Count == 0)
				return;

			//寫入 Tag 個數
			byte[] buffer12 = new byte[12];
			int tagCount = _Tags.Count;
			_BitConverter.GetBytes((ushort)tagCount, buffer12, 0);
			_Stream.Write(buffer12, 0, 2);

			//寫入 Tag
			long IfdDataOffset = (_Stream.Position + (tagCount * 12 + 4) - _ReferenceStreamPosition);
			for (int i = 0; i < tagCount; ++i)
			{
				//準備 Tag 資訊
				TagInfo tagInfo = _Tags[i];
				ExifTag tag = tagInfo.Tag;
				_BitConverter.GetBytes(tag.ID, buffer12, 0);
				_BitConverter.GetBytes((ushort)tag.Type, buffer12, 2);
				_BitConverter.GetBytes(tag.Count, buffer12, 4);

				//準備 Tag 資料
				if (tagInfo.DataOffset == null)
					Array.Copy(tag.Data, 0, buffer12, 8, 4);
				else
				{
					uint realDataOffset = (uint)(tagInfo.DataOffset.Value + IfdDataOffset);
					_BitConverter.GetBytes(realDataOffset, buffer12, 8);
				}

				//寫入資料流
				tagPositions[tag.Key] = _Stream.Position;
				_Stream.Write(buffer12, 0, 12);
			}

			//保留指向下一個 IFD 的空間
			nextIfdFieldPosition = _Stream.Position;
			buffer12[0] = buffer12[1] = buffer12[2] = buffer12[3] = 0;
			_Stream.Write(buffer12, 0, 4);

			//寫入資料區資料
			if (_IfdDataStream.Position > 0)
			{
				_IfdDataStream.Position = 0;
				_IfdDataStream.CopyTo(_Stream);
			}

			//完成
			_Tags.Clear();
			_TagIdTable.Clear();
			_IfdDataStream.Position = 0;
			_IfdDataStream.SetLength(0);
		}
        #endregion
		#region ReferenceStreamPosition
		/// <summary>
		/// 取得資料流起始參考位置，資料位移的計算將參考此值。
		/// </summary>
		public long ReferenceStreamPosition
		{
			get
			{
				return _ReferenceStreamPosition;
			}
		}
		#endregion
		#region Stream
		/// <summary>
		/// 取得提供 IFD 資料寫入的資料流。
		/// </summary>
		public Stream Stream
		{
			get
			{
				return _Stream;
			}
		}
		#endregion
		#region WriteTag
		/// <summary>
		/// 準備寫入 EXIF Tag，但不會將實際資料寫入至資料流。
		/// </summary>
		/// <param name="tag">準備寫入的 EXIF Tag。</param>
		/// <exception cref="ArgumentException">相同 ID 的 EXIF Tag 已經被寫入。</exception>
		/// <exception cref="ArgumentNullException">tag 為 Null (Visual Basic 為 Nothing)。</exception>
		public void WriteTag(ExifTag tag)
		{
			//確認參數
			if (tag == null)
				throw new ArgumentNullException("tag");
			if (!_TagIdTable.Add(tag.ID))
				throw new ArgumentException();

			//確認資料長度
			byte[] data = tag.Data;
			if (data == null)
				data = new byte[4];
			else if (data.Length < 4)
			{
				data = new byte[4];
				Array.Copy(tag.Data, data, tag.Data.Length);
			}

			//轉換 Byte-Ordering
			if (tag.BitConverter.IsLittleEndian != _BitConverter.IsLittleEndian)
			{
				int elementSize;
				switch (tag.Type)
				{
					case ExifTagType.URational:
					case ExifTagType.Rational:
						data.ChangeByteOrdering(0, data.Length, 4);
						break;
					case ExifTagType.Byte:
					case ExifTagType.AsciiString:
					case ExifTagType.SByte:
						break;
					default:
						elementSize = ExifTag.GetElementSize(tag.Type);
						if (elementSize > 0)
							data.ChangeByteOrdering(0, data.Length, elementSize);
						break;
				}
			}

			//將資料寫入資料區
			long? dataOffset;
			if (data.Length > 4)
			{
				dataOffset = _IfdDataStream.Position;
				_IfdDataStream.Write(data, 0, data.Length);
			}
			else
				dataOffset = null;

			//建立 Tag 資訊
			tag = new ExifTag(_BitConverter, tag.Ifd, tag.ID, tag.Type, tag.Count, data);
			_Tags.Add(new TagInfo(tag, dataOffset));
		}
		#endregion
	}
}
